DS 8. $x \to \hat{y}$ 가 되는 과정 그리기, Layer의 개념, Keras를 이용한 풀이, 여러가지 회귀모형의 적합과 학습과정의 모니터링
작성완료
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.experimental.numpy as tnp
tnp.experimental_enable_numpy_behavior()
import graphviz
def gv(s): return graphviz.Source('digraph G{ rankdir="LR"'+s + '; }')
- 단순회귀분석의 예시
- $\hat{y}_i = \hat{\beta}_0 + \hat{\beta}_1 x_i, \quad i=1,2,\dots,n$
(표현1)
gv('''
"1" -> "β̂₀ + xₙ*β̂₁, bias=False"[label="* β̂₀"]
"xₙ" -> "β̂₀ + xₙ*β̂₁, bias=False"[label="* β̂₁"]
"β̂₀ + xₙ*β̂₁, bias=False" -> "ŷₙ"[label="identity"]
"." -> "...................................."[label="* β̂₀"]
".." -> "...................................."[label="* β̂₁"]
"...................................." -> "..."[label=" "]
"1 " -> "β̂₀ + x₂*β̂₁, bias=False"[label="* β̂₀"]
"x₂" -> "β̂₀ + x₂*β̂₁, bias=False"[label="* β̂₁"]
"β̂₀ + x₂*β̂₁, bias=False" -> "ŷ₂"[label="identity"]
"1 " -> "β̂₀ + x₁*β̂₁, bias=False"[label="* β̂₀"]
"x₁" -> "β̂₀ + x₁*β̂₁, bias=False"[label="* β̂₁"]
"β̂₀ + x₁*β̂₁, bias=False" -> "ŷ₁"[label="identity"]
''')
- 표현1 소감:
- 다 똑같은 그림의 반복이 아닌가?
(표현2)
- 그냥 아래와 같이 그리고 "모든 $i=1,2,3,\dots,n$에 대하여 $\hat{y}_i$을 아래의 그림과 같이 그린다"고 하면 될것 같다.
gv('''
"1" -> "β̂₀ + xᵢ*β̂₁, bias=False"[label="* β̂₀"]
"xᵢ" -> "β̂₀ + xᵢ*β̂₁, bias=False"[label="* β̂₁"]
"β̂₀ + xᵢ*β̂₁, bias=False" -> "ŷᵢ"[label="identity"]
''')
(표현3)
- 그런데 "모든 $i=1,2,3,\dots,n$에 대하여 $\hat{y}_i$을 아래의 그림과 같이 그린다" 라는 언급자체도 반복할 필요가 없을 것 같다. 단순히 아래와 같이 그려도 무방할듯 하다.
gv('''
"1" -> "β̂₀ + x*β̂₁, bias=False"[label="* β̂₀"]
"x" -> "β̂₀ + x*β̂₁, bias=False"[label="* β̂₁"]
"β̂₀ + x*β̂₁, bias=False" -> "ŷ"[label="identity"]
''')
(표현4)
- 위의 모델은 아래와 같이 쓸 수 있다. ($\beta_0$를 바이어스로 표현)
gv('''
"x" -> "x*β̂₁, bias=True"[label="*β̂₁"] ;
"x*β̂₁, bias=True" -> "ŷ"[label="indentity"] ''')
- 실제로는 이 표현을 많이 사용함
(표현5)
- 벡터버전으로 표현하면 아래와 같다. 이 경우에는 ${\bf X}=[1,x]$에 포함된 1이 bias의 역할을 해주므로 bias = False 임.
gv('''
"X" -> "X@β̂, bias=False"[label="@β̂"] ;
"X@β̂, bias=False" -> "ŷ"[label="indentity"] ''')
(표현5)'
- 딥러닝에서는 $\hat{\boldsymbol{\beta}}$ 대신에 $\hat$을 라고 표현한다.
gv('''
"X" -> "X@Ŵ, bias=False"[label="@Ŵ"] ;
"X@Ŵ, bias=False" -> "ŷ"[label="identity"] ''')
- 실제로는 표현4~6를 잘 사용한다.
- (표현4~6)의 그림은 레이어로 설명할 수 있다.
- 레이어는 항상 아래와 같은 규칙을 가진다.
- 첫 동그라미는 레이어의 입력이다.
- 첫번째 화살표는 선형변환을 의미한다.
- 두번째 동그라미는 선형변환의 결과이다. (이때 bias가 false인지 true인지에 따라서 실제 수식이 조금 다름)
- 두번째 화살표는 두번째 동그라미에 어떠한 함수 $f$를 취하는 과정을 의미한다. (우리의 그림에서는 $f(x)=x$)
- 세번째 동그라미는 레이어의 최종출력이다.
- 꽤 복잡한데, 결국 레이어를 만들때 위의 그림들을 의미하도록 하려면 아래의 4개의 요소만 필요하다.
1. 레이어의 입력차원
2. 선형변환의 결과로 얻어지는 차원
3. 선형변환에서 바이어스의 사용유무
4. 함수 $f$
- 중요1: 1,2가 결정되면 자동으로 $\hat$의 차원이 결정된다.
(예시)
- 레이어의 입력차원=2, 선형변환의 결과로 얻어지는 차원=1: $\hat{\bf W}$는 (2,1) 매트릭스
- 레이어의 입력차원=20, 선형변환의 결과로 얻어지는 차원=5: $\hat{\bf W}$는 (20,5) 매트릭스
- 레이어의 입력차원=2, 선형변환의 결과로 얻어지는 차원=50: $\hat{\bf W}$는 (2,50) 매트릭스
- 중요2: 이중에서 절대 생략불가능 것은 "2. 선형변환의 결과로 얻어지는 차원" 이다.
- 레이어의 입력차원: 실제 레이어에 데이터가 들어올 때 데이터의 입력차원을 컴퓨터 스스로 체크하여 $\hat{\bf W}$의 차원을 결정할 수 있음.
- 바이어스의 사용유무는 기본적으로 사용한다고 가정한다.
- 함수 $f$: 기본적으로 항등함수를 가정하면 된다.
기본뼈대:
1.net생성
2.add(layer)
3.compile(opt,loss)
4.fit(data,epochs)
2. layer를 생성: x에서 y_hat을 만드는 방법을 정의하는 것을 의미
3. compile(opt,loss)의 의미: 손실함수를 정의하고 손실함수를 최소화시키는 반복 알고리즘을 결정할 옵티마이저를 정하는 것을 의미
4. fit(): 반복 학습
- 데이터
tnp.random.seed(50000)
N=200
x=tnp.linspace(0,1,N)
epsilon=tnp.random.randn(N)*0.5
y=2.5+4*x+epsilon
X=tf.stack([tf.ones(N,dtype='float64'),x],axis=1)
- 데이터 정리
y=y.reshape(-1)
x.shape,y.shape
1. net 생성
net=tf.keras.Sequential()
2. net.add(layer)
layer=tf.keras.layers.Dense(1)
- Dense(n): 선형변환의 결과로 얻어지는 n차원, 여기서는 1차원이기에 Dense(1)
- 숨겨진 의미: 레이어의 입력차원= 데이터를 넣어보고 결정, 바이어스= 디폴트 값을 따름(디폴트:use_bias=true), 함수= 디폴트 값을 따름(디폴트:f(x)=x)
net.add(layer)
3. net.compile(opt,loss_fn)
net.compile(tf.keras.optimizers.SGD(0.1),tf.keras.losses.MSE)
- optimizer와 loss_fn을 바로 선언해주었음
4. net.fit(x,y,epochs)
net.fit(x,y,epochs=1000,verbose=0,batch_size=N)
- 오류가 남..!
- 이는 x,y의 shape 에러인데, 아래와 같이 선언해주어야 한다. 지난 풀이와 다른 점인..!
x=x.reshape(N,1)
y=y.reshape(N,1)
x.shape,y.shape
- x와 y의 shape를 (n,1)로 설정해주어야 한다!
net=tf.keras.Sequential()
layer=tf.keras.layers.Dense(1)
net.add(layer)
net.compile(tf.keras.optimizers.SGD(0.1),tf.keras.losses.MSE)
net.fit(x,y,epochs=1000,verbose=0,batch_size=N)
- batch_size = N 일 경우에 경사하강법이 적용되고 batch_size != N 일 경우에 확률적 경사하강법이 적용된다
- 추후에는 확률적 경사하강법을 사용할 것임
net.weights
- 비슷한 값이 나왔다.
- 데이터
tnp.random.seed(50000)
N=200
x=tnp.linspace(0,1,N)
epsilon=tnp.random.randn(N)*0.5
y=2.5+4*x+epsilon
- 선언
X=tf.stack([tf.ones(N,dtype='float64'),x],axis=1)
y=y.reshape(N,1)
X.shape,y.shape
net=tf.keras.Sequential()
layer=tf.keras.layers.Dense(1,use_bias=False)
net.add(layer)
net.compile(tf.keras.optimizers.SGD(0.1),tf.keras.losses.MSE)
net.fit(X,y,epochs=1000,verbose=0,batch_size=N)
net.weights
- 비슷한 결과가 나왔다
- 비슷하지만 디폴트로 바이어스=true 설정한 코드
tnp.random.seed(50000)
N=200
x=tnp.linspace(0,1,N)
epsilon=tnp.random.randn(N)*0.5
y=2.5+4*x+epsilon
X=tf.stack([tf.ones(N,dtype='float64'),x],axis=1)
y=y.reshape(N,1)
X.shape,y.shape
net=tf.keras.Sequential()
layer=tf.keras.layers.Dense(1)
net.add(layer)
net.compile(tf.keras.optimizers.SGD(0.1),tf.keras.losses.MSE)
net.fit(X,y,epochs=1000,verbose=0,batch_size=N)
net.weights
- 결과값을 보면 $β_1$은 잘 추론했지만, $β_0$는 $β_0 + alpha$로 나뉘어서 추정되었다. $β_0 = 1.3234154, alpha = 1.1670585$로 합하면 $2.5$에 근사한다.
- 이는 layer를 설정할 때 바이어스를 False로 바꿔주지 않았기 때문이다.(바이어스는 디폴트가 True이기 때문에 바이어스를 쓰지 않을 경우에 False로 바꿔주어야 한다.
- tf.keras.layers.Dense()를 통해 layer를 만드는 코드를 살펴보자.
(1) 아래는 모두 같은 코드이다.
1. tf.keras.layers.Dense(1)
2. tf.keras.layers.Dense(units=1)
- Dense()에 activation을 입력해주지 않으면(activatiion=None) 디폴트 값인 activation='linear'로 반환해준다.
3. tf.keras.layers.Dense(units=1,activation='linear'), 여기서 activation='linear'은 항등함수이다.
4. tf.keras.layers.Dense(units=1,activation='linear',use_bias=True), bias에 관한 디폴트 값은 True이다.
(2) 아래의 코드5,6는 위의 코드들과 살짝 다른코드이다. (코드5와 코드6은 같은코드)
5. tf.keras.layers.Dense(1,input_dim=2)
6. tf.keras.layers.Dense(1,input_shape=(2,))
(3) 아래는 사용불가능한 코드이다.
7. tf.keras.layers.Dense(1,input_dim=(2,))
8. tf.keras.layers.Dense(1,input_shape=2)
np.array([1,2]).shape
- 왜 input_dim을 사용해야하지? 불편한데,,?!
net1=tf.keras.Sequential()
net1.add(tf.keras.layers.Dense(1,use_bias=False))
net2=tf.keras.Sequential()
net2.add(tf.keras.layers.Dense(1,use_bias=False,input_dim=2))
net1.weights
net2.weights
- net1의 weight를 구하고자 하면 입력차원이 입력되지 않아(입력차원을 모르기 때문에) 에러가 난다.
- net2의 weight는 입력차원이 입력되어 그 시점에서의 weight가 나타난다.
net1.summary()
net2.summary()
- summary()도 마찬가지로, net1의 경우 입력차원이 정의되지 않아(입력차원을 모르기 때문에) 에러가 난다.
- net2의 경우 입력차원이 정의되어 net2에 대한 정보가 나타난다.
step0: 데이터 정리
y=y.reshape(N,1)
x=x.reshape(N,1)
x.shape,y.shape
step1: net 생성
net=tf.keras.Sequential()
step2: net.add(layer)
layer=tf.keras.layers.Dense(1,input_dim=1) #바이어스가 필요하기 때문에 바이어스는 디폴트(bias=True)로 설정
net.add(layer)
- 임의의 초기값을 설정하는 방법(조금 복잡..!)
net.weights
- 입력차원을 설정해주었기 때문에 weight가 출력됨!
net.get_weights()
- net.get_weights()라는 함수가 있음
- 이는 1.4751679, 0을 리턴했음
- 1.4751679는 위 net.weights의 첫 번째 값과 같고, 0은 net.weights의 두번째 값과 같음을 알 수 있음
- net.weights의 두번째 값에 dense_11/bias:0가 리턴된 것으로 보아 이는 바이어스 값으로 보임
- 그렇다면 net.weights의 첫번째 값은 $\beta_1$일 것 같음
- 즉, weight, bias 순으로 출력됨
net.set_weights
- net.set_weights()라는 함수가 있음
- 이는 초기값을 설정하는 함수임
- 도움말을 참조하면 layer_b.set_weights(layer_a.get_weights()) 와 같은 방식으로 사용하는 것을 알 수 있음
- 한 번 사용해보자!
_w = net.get_weights()
_w?
- 길이가 2인 리스트임
_w[0]
type(_w[0])
- 각 원소는 넘파이 array이 임
np.array([[1.4751679]], dtype=np.float32)
- 이는 위의 _w[0] 값과 같음
np.array([[0.]], dtype=np.float32)
- 이는 위의 _w[1] 값과 같음
- 그렇다면 다음과 같이 할 수 있음
net.set_weights(
[np.array([[1.4751679]], dtype=np.float32),
np.array([0.0], dtype=np.float32)]
)
net.get_weights()
- 초기값을 $\beta_1=10$, $\beta_0=-5$로 설정
net.set_weights(
[np.array([[10.0]], dtype=np.float32),
np.array([-5.0], dtype=np.float32)]
)
net.get_weights()
step3: net.compile()
net.compile(tf.keras.optimizers.SGD(0.1),tf.losses.MSE)
step4: net.fit
net.fit(x,y,epochs=1000,verbose=0,batch_size=N)
net.weights
- 같은 값이 나왔음
step0: 데이터 정리
X.shape,y.shape
step1: net 생성
net=tf.keras.Sequential()
step2: net.add(layer)
layer=tf.keras.layers.Dense(1,use_bias=False,input_dim=2)
net.add(layer)
step3: net.compile()
net.get_weights()
net.set_weights(
[np.array([[10.0],[-5.0]], dtype=np.float32)]
)
net.get_weights()
net.compile(tf.keras.optimizers.SGD(0.1), tf.keras.losses.MSE)
step4: net.fit
net.fit(X,y,epochs=1000,verbose=0,batch_size=N)
net.get_weights()
- 같은 값이 나왔음
step0: 데이터 정리
X.shape,y.shape
step1: net 생성
net=tf.keras.Sequential()
step2: net.add(layer)
layer=tf.keras.layers.Dense(1,use_bias=False)
net.add(layer)
step3: net.compile()
loss_fn = lambda y, yhat : (y-yhat).T @ (y-yhat) / N
net.compile(tf.keras.optimizers.SGD(0.1), loss_fn)
step4: net.fit
net.fit(X,y,epochs=1000,verbose=0,batch_size=N)
net.weights
- 값이 잘 나왔음
step0: 데이터 정리
X.shape,y.shape
step1: net 생성
net=tf.keras.Sequential()
step2: net.add(layer)
net.add(tf.keras.layers.Dense(1,use_bias=False))
step3: net.compile()
net.compile(tf.keras.optimizers.SGD(0.1), loss='mse')
- 손실함수를 지정할 때, loss= 'mse'라고 입력하면 내장되어 있는 'mse'를 찾아서 compile 해준다!
step4: net.fit
net.fit(X,y,epochs=1000,verbose=0,batch_size=N)
net.weights
step0: 데이터 정리
X.shape,y.shape
step1: net 생성
net=tf.keras.Sequential()
step2: net.add(layer)
net.add(tf.keras.layers.Dense(1,use_bias=False))
step3: net.compile()
net.compile(optimizer='sgd', loss='mse')
- 옵티마이저를 지정할 때 위에서 손실함수를 지정한 것처럼 optimizer='sgd'라고 입력하면 내장되어 있는 'sgd'를 찾아서 compile 해준다!
step4: net.fit
net.fit(X,y,epochs=1000,verbose=0,batch_size=N)
net.weights
- 다른 결과가 나타났음...!
- 이는 내장되어 있는 'sgd'에 학습율이 0.01로 설정되어있기 때문
- 이를 해결하기 위해선, 학습율을 바꿔주거나 epoc을 늘려주면 됨
- 학습률 확인
net.optimizer.learning_rate
- 설정된 학습률이 0.01임
net.optimizer.lr = tf.Variable(0.1,dtype=tf.float32)
net.optimizer.lr
- 학습률이 0.1로 바뀌었음!
net.optimizer.lr = tf.Variable(0.2)
net.optimizer.lr
- dtype은 설정해주지 않아도 되는 것으로 보임
net.optimizer.lr = 0.1
net.optimizer.lr
- 0.1만 입력해주어도 됨!
- 다시
X.shape,y.shape
net=tf.keras.Sequential()
net.add(tf.keras.layers.Dense(1,use_bias=False))
net.compile(optimizer='sgd', loss='mse')
net.optimizer.lr = tf.Variable(0.1, dtype=tf.float32)
net.optimizer.lr
net.fit(X,y,epochs=1000,verbose=0,batch_size=N)
net.weights
- 학습이 잘 됨!
- optimizer='SGD'라고 입력해주어도 실행될까?
X.shape,y.shape
net=tf.keras.Sequential()
net.add(tf.keras.layers.Dense(1,use_bias=False))
net.compile(optimizer='SGD', loss='mse')
net.optimizer.lr = tf.Variable(0.1, dtype=tf.float32)
net.optimizer.lr
net.fit(X,y,epochs=1000,verbose=0,batch_size=N)
net.weights
- 잘 된다!
- 학습률을 변경하지 않고 epoc를 늘려보자
X.shape,y.shape
net=tf.keras.Sequential()
net.add(tf.keras.layers.Dense(1,use_bias=False))
net.compile(optimizer='sgd', loss='MSE')
net.fit(X,y,epochs=5000,verbose=0,batch_size=N)
net.weights
- 값은 잘 나왔음!
- 그런데 epoc를 늘리면 실행시간이 길어짐,,,
from matplotlib import animation
plt.rcParams["animation.html"] = "jshtml"
$y_i \approx \beta_0 +\beta_1 x_i$
np.random.seed(50000)
n=100
x=np.random.randn(N)
epsilon=np.random.randn(N)*0.5
y=2.5+4*x+epsilon
X=np.stack([np.ones(N),x],axis=1)
y=y.reshape(N,1)
beta_hat=np.array([-3,-2]).reshape(2,1)
beta_hat
yhat=X@beta_hat
plt.plot(x,y,'o')
plt.plot(x,yhat.reshape(-1),'-')
- 현재 상황
- 최적의 적합선을 찾아보자!
- slope 설정 & 업데이트
slope= (2*X.T @ yhat - 2*X.T @ y)/ N
beta_hat2 = beta_hat - 0.1*slope
yhat2= X @ beta_hat2
plt.plot(x,y,'o')
plt.plot(x,yhat.reshape(-1),'-')
plt.plot(x,yhat2.reshape(-1),'-')
- 한 번 업데이트 한 상황!
- 이를 반복해보자!
beta_hat=np.array([-3.0,-2.0]).reshape(2,1) # beta_hat 초기값
beta_hat
beta_hats = beta_hat.copy() # beta_hat 업데이트를 기록
for i in range(1,3): # 30번 반복해보자!
yhat = X @ beta_hat
slope = (2*X.T @ yhat - 2*X.T @ y) / N # 기울기
beta_hat = beta_hat - 0.1*slope # beta_hat 업데이트
beta_hats = np.concatenate([beta_hats,beta_hat],axis=1) # 업데이트를 기록
beta_hats
-
np.concatenate([beta_hats,beta_hat],axis=1)는 새로운 열에 업데이트된 값을 입력해준다!
beta_hats = beta_hat.copy() # beta_hat 업데이트를 기록
for i in range(1,30): # 30번 반복해보자!
yhat = X @ beta_hat
slope = (2*X.T @ yhat - 2*X.T @ y) / N # 기울기
beta_hat = beta_hat - 0.1*slope # beta_hat 업데이트
beta_hats = np.concatenate([beta_hats,beta_hat],axis=1) # 업데이트를 기록
beta_hats
- 베타값을 잘 추정했음!
- 추정치를 구분해서 저장해보자
beta_hats[0]
b0hats=beta_hats[0].tolist()
b1hats=beta_hats[1].tolist()
- fig 설정
fig = plt.figure(); fig.set_figheight(5); fig.set_figwidth(12)
np.linalg.inv(X.T@X) @ X.T @ y
- 최적의 β값
- 목표:
- 왼쪽 그림: 초기의 β값에서의 적합선 -> 최적의 β값에서의 적합선으로 가는 과정
- 오른쪽 그림: (loss 함수에서) 초기의 β값 -> 최적의 β값으로 찾아가는 과정
ax1= fig.add_subplot(1,2,1)
ax2= fig.add_subplot(1,2,2,projection='3d')
# ax1: 왼쪽그림
ax1.plot(x,y,'o')
line, = ax1.plot(x,b0hats[0] + b1hats[0]*x)
# ax2: 오른쪽그림
β0,β1 = np.meshgrid(np.arange(-6,11,0.25),np.arange(-6,11,0.25),indexing='ij')
β0=β0.reshape(-1)
β1=β1.reshape(-1)
loss_fn = lambda b0,b1: np.sum((y-b0-b1*x)**2)
loss = list(map(loss_fn, β0,β1))
ax2.scatter(β0,β1,loss,alpha=0.02)
ax2.scatter(2.5451404,3.94818596,loss_fn(2.5451404,3.94818596),s=200,marker='*')
def animate(i):
line.set_ydata(b0hats[i] + b1hats[i]*x)
ax2.scatter(b0hats[i],b1hats[i],loss_fn(b0hats[i],b1hats[i]),color="grey")
ani = animation.FuncAnimation(fig,animate,frames=30)
ani
- $α$를 어떻게 잡는지가 중요!
- $α = 0.1$은 적당한 것으로 보임!
- $α = 0.3$은 다소 빠른감이 있음
- $α = 0.5$는 너무 빠름
- $α = 0.9$는 수렴하기는 하지만 위험해보임
- $α = 1.0$은 수렴하지 않음..!
- $α = 1.2$는 터짐..! : 너무 큰 $α$를 잡으면 수렴되지 않을 수 있다! 천천히 가더라도 적당한 $α$를 잡아서 학습하자!
1. np.meshgrid():
(이 예제에서)
(0,4)
(0,5)
(0,6)
(1,4)
(1,5)
(1,6)
(2,4)
(2,5)
(2,6)
np.meshgrid(np.array([0,1,2]),np.array([4,5,6]))
np.meshgrid(np.array([0,1,2]),np.array([4,5,6]),indexing='ij')
β0, β1 = np.meshgrid(np.array([0,1,2]),np.array([4,5,6]),indexing='ij')
β0
β0, β1
β0.reshape(-1), β1.reshape(-1)
β0 = β0.reshape(-1)
β1 = β1.reshape(-1)
β0, β1
2. map(lambda :)
lambda 함수에 입력값을 넣어 계산
lambda x,y : x+y # 입력 : 출력
map(lambda x,y: x+y, β0,β1)
- 이를 실행한 순간 계산이 된 것!
- 계산결과는..?
list(map(lambda x,y: x+y, β0,β1))
- 리스트화 시켜주면 결과가 나옴
- β0, β1에 대한 loss를 계산
list(map(lambda b0,b1: np.sum(y-b0-b1*x)**2, β0,β1))
- β0, β1 값을 변경해서 계산
β0, β1 = np.meshgrid(np.arange(-6,11,0.25),np.arange(-6,11,0.25),indexing='ij')
β0 = β0.reshape(-1)
β1 = β1.reshape(-1)
β0, β1
$y_i \approx \beta_0 +\beta_1 e^{-x_i}$
- 데이터
np.random.seed(50000)
N= 100
x= np.linspace(-1,1,N)
epsilon = np.random.randn(N)*0.5
y= 2.5+4*np.exp(-x)+epsilon
plt.plot(x,y,'o')
- 데이터 정리
X= np.stack([np.ones(N),np.exp(-x)],axis=1)
y= y.reshape(N,1)
beta_hat = np.array([-3,-2]).reshape(2,1)
beta_hats = beta_hat.copy() # shallow copy, deep copy
for i in range(1,30):
yhat = X@beta_hat
slope = (2*X.T@X@beta_hat - 2*X.T@y) /N
beta_hat = beta_hat - 0.05*slope # 학습율을 0.05로 설정
beta_hats = np.concatenate([beta_hats,beta_hat],axis=1) # column에 업데이트 기록
beta_hats # 업데이트된 β추정치
b0hats=beta_hats[0].tolist()
b1hats=beta_hats[1].tolist()
np.linalg.inv(X.T@X)@X.T@y
- 최적의 추정치
- fig 설정
fig = plt.figure(); fig.set_figheight(5); fig.set_figwidth(12)
- 애니메이션
ax1= fig.add_subplot(1,2,1)
ax2= fig.add_subplot(1,2,2,projection='3d')
# ax1: 왼쪽그림
ax1.plot(x,y,'o')
line, = ax1.plot(x,b0hats[0] + b1hats[0]*np.exp(-x)) # 예제 1 코드에서 x대신 np.exp(-x)
# ax2: 오른쪽그림
β0,β1 = np.meshgrid(np.arange(-6,11,0.25),np.arange(-6,11,0.25),indexing='ij')
β0=β0.reshape(-1)
β1=β1.reshape(-1)
loss_fn = lambda b0,b1: np.sum((y-b0-b1*np.exp(-x))**2) # 예제 1 코드에서 x대신 np.exp(-x)
loss = list(map(loss_fn, β0,β1))
ax2.scatter(β0,β1,loss,alpha=0.02)
ax2.scatter(2.46307644,3.99681332,loss_fn(2.46307644,3.99681332),s=200,marker='*')
def animate(i):
line.set_ydata(b0hats[i] + b1hats[i]*np.exp(-x)) # b0hats[i] + b1hats[i]*np.exp(-x) -> yhat # 예제 1 코드에서 x대신 np.exp(-x)
ax2.scatter(b0hats[i],b1hats[i],loss_fn(b0hats[i],b1hats[i]),color="grey")
ani = animation.FuncAnimation(fig,animate,frames=30)
ani
$y_i \approx \beta_0 +\beta_1 e^{-x_i} + \beta_2 \cos(5x_i)$
- 데이터
np.random.seed(50000)
N= 100
x= np.linspace(-1,1,N)
epsilon = np.random.randn(N)*0.5
y= 2.5+4*np.exp(-x) + 5*np.cos(5*x) + epsilon
plt.plot(x,y,'o')
- 데이터 정리
X=np.stack([np.ones(N),np.exp(-x),np.cos(5*x)],axis=1)
y=y.reshape(N,1)
beta_hat = np.array([-3,-2,-1]).reshape(3,1)
beta_hats = beta_hat.copy()
for i in range(1,30):
yhat = X@beta_hat
slope = (2*X.T@X@beta_hat -2*X.T@y) /N
beta_hat = beta_hat - 0.1 * slope
beta_hats= np.concatenate([beta_hats,beta_hat],axis=1) # 업데이트 기록
beta_hats
- β0, β1, β2까지 있으니 3개의 값이 나왔음
b0hats = beta_hats[0].tolist()
b1hats = beta_hats[1].tolist()
b2hats = beta_hats[2].tolist()
(b0hats,b1hats,b2hats) = beta_hats
b0hats,b1hats,b2hats = beta_hats # 튜플 언패킹
np.linalg.inv(X.T@X) @ X.T @ y
- 최적의 β추정치
- fig 설정
fig = plt.figure(); fig.set_figheight(5); fig.set_figwidth(12)
- 애니메이션
ax1= fig.add_subplot(1,2,1)
ax2= fig.add_subplot(1,2,2,projection='3d')
# ax1: 왼쪽그림
ax1.plot(x,y,'o')
line, = ax1.plot(x,b0hats[0] + b1hats[0]*np.exp(-x) + b2hats[0]*np.cos(5*x))
# ax2: 오른쪽그림
# β0,β1 = np.meshgrid(np.arange(-6,11,0.25),np.arange(-6,11,0.25),indexing='ij')
# β0=β0.reshape(-1)
# β1=β1.reshape(-1)
# loss_fn = lambda b0,b1: np.sum((y-b0-b1*np.exp(-x))**2)
# loss = list(map(loss_fn, β0,β1))
# ax2.scatter(β0,β1,loss,alpha=0.02)
# ax2.scatter(2.46307644,3.99681332,loss_fn(2.46307644,3.99681332),s=200,marker='*')
def animate(i):
line.set_ydata(b0hats[i] + b1hats[i]*np.exp(-x) + b2hats[i]*np.cos(5*x))
# ax2.scatter(b0hats[i],b1hats[i],loss_fn(b0hats[i],b1hats[i]),color="grey")
ani = animation.FuncAnimation(fig,animate,frames=30)
ani
$y_i \approx \beta_0 +\beta_1 e^{-x_i} + \beta_2 \cos(5x_i)$
- 데이터
np.random.seed(50000)
N= 100
x= np.linspace(-1,1,N)
epsilon = np.random.randn(N)*0.5
y= 2.5+4*np.exp(-x) + 5*np.cos(5*x) + epsilon
- 데이터정리
X=np.stack([np.ones(N),np.exp(-x),np.cos(5*x)],axis=1)
y=y.reshape(N,1)
net = tf.keras.Sequential()
# 2: add layer
net.add(tf.keras.layers.Dense(1,use_bias=False))
# 3: compile
net.compile(tf.optimizers.SGD(0.1), loss='mse')
# 4: fit
net.fit(X,y,epochs=30, batch_size=N)
- loss가 잘 줄어들고 있는지 확인!
- loss값의 변화가 커졌다 작아졌다를 반복하거나 급격하게 줄어들지는 않는지 확인해볼 필요가 있음(epoc이 지금처럼 작을 때만 가능)
net.weights
plt.plot(x,y,'o')
plt.plot(x,(X@net.weights).reshape(-1),'--')
- 적합한 회귀선이 추정되었음!
$y_i \approx \beta_0 +\beta_1 e^{-x_i}$
np.random.seed(43052)
N= 100
x= np.linspace(-1,1,N)
epsilon = np.random.randn(N)*0.5
y= 2.5+4*np.exp(-x) +epsilon
X=np.stack([np.ones(N),np.exp(-x)],axis=1)
y=y.reshape(N,1)
net = tf.keras.Sequential()
# 2: add layer
net.add(tf.keras.layers.Dense(1,use_bias=False))
# 3: compile
net.compile(tf.optimizers.SGD(0.05), loss='mse')
# 4: fit
net.fit(X,y,epochs=30, verbose=0, batch_size=N)
net.weights
plt.plot(x,y,'o')
plt.plot(x,(X@net.weights).reshape(-1),'--')
- β0= 2.515 β1= 3.959이고 적합된 회귀직선은 $y_i = 2.515 +3.959 e^{-x_i}$이다.